/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package analisadorlexico;

import java.util.ArrayList;
import java.util.List;
import modelo.ErroLexico;
import modelo.IErro;
import modelo.Token;
import modelo.tipos.TipoErroLexico;
import modelo.tipos.TipoToken;

//rodrigo.f.silva@ufes.br
/**
 *
 * @author DEP
 */
public class AnalisadorLexico {

    private List<IErro> erros;
    private List<Token> tokens;
    private String codigoFonte;
    private String token = "";
    private char charAtual;
    private Integer index = 0;
    private int numLinha;

    public void setCodigoFonte(String codigo) {
        this.codigoFonte = codigo;
    }

    public List<Token> getTokens() {
        return tokens;
    }

    private void proximoChar() {
        try {
            charAtual = codigoFonte.charAt(++index);
        } catch (StringIndexOutOfBoundsException e) {
            charAtual = '\0';
            System.out.println(e.getMessage());

        }
    }

    public void analisar() {

        erros = new ArrayList<>();
        tokens = new ArrayList<>();
        index = 0;
        numLinha = 1;

        if (!codigoFonte.isEmpty()) {
            charAtual = codigoFonte.charAt(0);
        }

        while (index < codigoFonte.length()) {
            if (charAtual == '\0') {
                return;
            }

            if (charAtual == ' ' || charAtual == '\f' || charAtual == '\t') {
                token = "";
                proximoChar();
                continue;
            } else if (charAtual == '\n') {
                numLinha++;
                token = "";
                proximoChar();
                continue;
            } else if (charAtual == '\'') {
                token += charAtual;
                estadoLiteral();
            } else if (Character.isLetter(charAtual)) {
                token += charAtual;
                estadoIdentificador();
            } else if (Character.isDigit(charAtual)) {
                token += charAtual;
                estadoNumero();
            } else if (charAtual == '+' || charAtual == '-' || charAtual == '*' || charAtual == '/') {
                token += charAtual;
                estadoOperador();
            } else if (charAtual == ':') {
                token += charAtual;
                estadoAtribuicao();
            } else if (charAtual == ';') {
                token += charAtual;
                estadoDelimitador();
            } else if (charAtual == '.') {
                token += charAtual;
                estadoPonto();

            } else if (charAtual == ',') {
                token += charAtual;
                estadoSeparador();
            } else if (charAtual == '>' || charAtual == '<') {
                token += charAtual;
                estadoComparador();
            } else if (charAtual == '=') {
                token += charAtual;
                estadoComparador();
            } else if (charAtual == '{') {
                token += charAtual;
                estadoComentarioChaves();
            } else if (charAtual == '(') {
                token += charAtual;
                estadoParentesesAbre();
            } else if (charAtual == ')') {
                token += charAtual;
                estadoParentesesFecha();
            } else if (charAtual == '[') {
                token += charAtual;
                estadoColchetesAbre();
            } else if (charAtual == ']') {
                token += charAtual;
                estadoColchetesFecha();
            } else {
                token += charAtual;
                erros.add(new ErroLexico(construirTokenErroLexico(token, numLinha), TipoErroLexico.SIMBOLO_INEXISTENTE));
                proximoChar();
            }

        }
    }

    private void estadoSeparador() {
        construirSeparador(token, numLinha);
        proximoChar();
    }

    private void estadoComentarioBarra() {

        if (charAtual == '/') {
            proximoChar();
            while (charAtual != '\n' && charAtual != '\0') {
                token += charAtual;
                proximoChar();
            }
            token = "";
        } else {
            erros.add(new ErroLexico(construirTokenErroLexico(token, numLinha), TipoErroLexico.SIMBOLO_INEXISTENTE));
            proximoChar();
        }
    }

    private void estadoParentesesFecha() {
        construirParentesesFecha(token, numLinha);
        proximoChar();
    }

    private void estadoColchetesFecha() {
        construirColchetesFecha(token, numLinha);
        proximoChar();
    }

    private void estadoParentesesAbre() {
        int linhaAnterior = numLinha;
        String tkAnterior = token;
        proximoChar();
        if (charAtual == '*') {
            estadoComentario();
        } else {
            construirParentesesAbre(tkAnterior, linhaAnterior);
        }

    }

    private void estadoColchetesAbre() {
        construirColchetesAbre(token, numLinha);
        proximoChar();

    }

    private void estadoComentarioChaves() {
        proximoChar();
        while (charAtual != '}') {
            token += charAtual;
            if (charAtual == '\n') {
                numLinha++;
            }
            proximoChar();
            if (charAtual == '\0') {
                erros.add(new ErroLexico(construirTokenErroLexico(token, numLinha), TipoErroLexico.SIMBOLO_INEXISTENTE));
                return;
            }
        }
        token = "";
        proximoChar();
    }

    private void estadoComentario() {
        char proxchar = '0';
        token += charAtual;
        proximoChar();
        while (!(charAtual == '*' && proxchar == ')')) {
            if (charAtual == '\n') {
                numLinha++;
            }
            token += charAtual;
            proximoChar();
            try {
                proxchar = codigoFonte.charAt(index + 1);
            } catch (Exception e) {
                proxchar = '\0';
            }
            if (charAtual == '\0') {
                erros.add(new ErroLexico(construirTokenErroLexico(token, numLinha), TipoErroLexico.SIMBOLO_INEXISTENTE));
                return;
            }
        }

        proximoChar();
        proximoChar();
        token = "";
    }

    private void estadoComparador() {
        char charanterior = charAtual;
        proximoChar();
        if (charanterior == '=') {
            construirComparador(token, numLinha);
        } else if (charAtual == '=') {
            token += charAtual;
            construirComparador(token, numLinha);
            proximoChar();
        } else if (charanterior == '<' && charAtual == '>') {
            token += charAtual;
            construirComparador(token, numLinha);
            proximoChar();
        } else {
            construirComparador(token, numLinha);
            //        erros.add(new ErroLexico(construirTokenErroLexico(token, numLinha), TipoErroLexico.SIMBOLO_INEXISTENTE));
        }

    }

    private void estadoOperador() {
        int linhaAnterior = numLinha;
        String tkAnterior = token;
        proximoChar();
        if (charAtual == '/') {
            estadoComentarioBarra();
        } else {
            tokens.add(new Token(tkAnterior, TipoToken.OPERADOR, linhaAnterior));
            token = "";
        }

    }

    private void estadoDelimitador() {
        construirDelimitador(token, numLinha);
        proximoChar();
    }

    private void estadoPonto() {
        construirPonto(token, numLinha);
        proximoChar();
    }

    private void estadoAtribuicao() {
        int linhaAnterior = numLinha;
        String tkAnterior = token;
        proximoChar();
        if (charAtual == '=') {
            token += charAtual;
            construirAtribuicao(token, numLinha);
            proximoChar();
        } else {
            construirAtribuicao(tkAnterior, linhaAnterior);
        }

    }

    private void estadoLiteral() {
        proximoChar();
        while (charAtual != '\'' && charAtual != '\n') {
            token += charAtual;
            proximoChar();
        }
        if (charAtual == '\'') {
            token += charAtual;
            construirLiteral(token, numLinha);
            proximoChar();
        } else {
            erros.add(new ErroLexico(construirTokenErroLexico(token, numLinha), TipoErroLexico.SIMBOLO_INEXISTENTE));
        }


    }

    private void estadoIdentificador() {
        proximoChar();
        while (Character.isLetterOrDigit(charAtual)) {
            token += charAtual;
            proximoChar();
        }
        if (charAtual != '.') {
            construirIdentificador(token, numLinha);
        } else {
            if (token.compareToIgnoreCase("fim") == 0) {
                construirIdentificador(token, numLinha);
            } else {
                while (charAtual != ' ' && charAtual != ',' && charAtual != '\n' && charAtual != '\t') {
                    token += charAtual;
                    proximoChar();
                }
                erros.add(new ErroLexico(construirTokenErroLexico(token, numLinha), TipoErroLexico.SIMBOLO_INEXISTENTE));
            }
        }

    }

    private void estadoNumero() {
        proximoChar();
        if (Character.isDigit(charAtual)) {
            token += charAtual;
            estadoNumero();
        } else if (charAtual == '.') {
            token += charAtual;
            estadoNumeroReal();
        } else if (Character.isLetter(charAtual)) {

            do {
                token += charAtual;
                proximoChar();
            } while (Character.isLetterOrDigit(charAtual) || charAtual == '.');
            erros.add(new ErroLexico(construirTokenErroLexico(token, numLinha), TipoErroLexico.SIMBOLO_INEXISTENTE));

        } else {
            construirNumeroInteiro(token, numLinha);
        }
    }

    private void estadoNumeroReal() {
        proximoChar();
        if (!Character.isDigit(charAtual)) {
            erros.add(new ErroLexico(construirTokenErroLexico(token, numLinha), TipoErroLexico.SIMBOLO_INEXISTENTE));
            return;
        }
        while (Character.isDigit(charAtual)) {
            token += charAtual;
            proximoChar();
        }

        if (Character.isLetter(charAtual)) {

            do {
                token += charAtual;
                proximoChar();
            } while (Character.isLetterOrDigit(charAtual) || charAtual == '.');
            erros.add(new ErroLexico(construirTokenErroLexico(token, numLinha), TipoErroLexico.SIMBOLO_INEXISTENTE));

        } else {
            construirNumeroReal(token, numLinha);
        }
    }

    private void construirLiteral(String token, int numLinha) {
        if (token.length() <= 3) {
            tokens.add(new Token(token, TipoToken.CHAR, numLinha));
        } else {
            tokens.add(new Token(token, TipoToken.LITERAL, numLinha));
        }
        this.token = "";
    }

    private void construirColchetesAbre(String token, int numLinha) {
        tokens.add(new Token(token, TipoToken.COLCHETES_ABRE, numLinha));
        this.token = "";
    }

    private void construirColchetesFecha(String token, int numLinha) {
        tokens.add(new Token(token, TipoToken.COLCHETES_FECHA, numLinha));
        this.token = "";
    }

    private void construirParentesesAbre(String token, int numLinha) {
        tokens.add(new Token(token, TipoToken.PARENTESES_ABRE, numLinha));
        this.token = "";
    }

    private void construirParentesesFecha(String token, int numLinha) {
        tokens.add(new Token(token, TipoToken.PARENTESES_FECHA, numLinha));
        this.token = "";
    }

    private void construirIdentificador(String token, int numLinha) {
        PalavrasReservadas reservadas = PalavrasReservadas.getInstance();
        if (reservadas.isPalavraReservada(token)) {
            tokens.add(new Token(token, TipoToken.PALAVRA_RESERVADA, numLinha));
            this.token = "";
        } else {
            tokens.add(new Token(token, TipoToken.IDENTIFICADOR, numLinha));
            this.token = "";
        }
    }

    private void construirSeparador(String token, int numLinha) {
        tokens.add(new Token(token, TipoToken.SEPARADOR, numLinha));
        this.token = "";
    }

    private void construirNumeroInteiro(String token, int numLinha) {
        tokens.add(new Token(token, TipoToken.NUMERO_INTEIRO, numLinha));
        this.token = "";
    }

    private void construirNumeroReal(String token, int numLinha) {
        tokens.add(new Token(token, TipoToken.NUMERO_REAL, numLinha));
        this.token = "";
    }

    private void construirDelimitador(String token, int numLinha) {
        tokens.add(new Token(token, TipoToken.PONTOEV, numLinha));
        this.token = "";
    }

    private void construirPonto(String token, int numLinha) {
        tokens.add(new Token(token, TipoToken.PONTO, numLinha));
        this.token = "";
    }

    private void construirAtribuicao(String token, int numLinha) {
        if (token.compareTo(":") == 0) {
            tokens.add(new Token(token, TipoToken.DOIS_PONTOS, numLinha));
        } else {
            tokens.add(new Token(token, TipoToken.ATRIBUICAO, numLinha));
        }
        this.token = "";
    }

    private void construirComparador(String token, int numLinha) {
        tokens.add(new Token(token, TipoToken.COMPARADOR, numLinha));
        this.token = "";
    }

    private Token construirTokenErroLexico(String token, int numLinha) {
        Token t = new Token(token, TipoToken.INVALIDO, numLinha);
        this.token = "";
        return t;
    }

    public String getErros() {
        String erroString = "";
        for (IErro erro : erros) {
            ErroLexico erroLexico = (ErroLexico) erro;
            erroString += erroLexico.errosToString();
        }
        return erroString;
    }
}
